package lpcn.xbee;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import com.rapplogic.xbee.api.PacketListener;
import com.rapplogic.xbee.api.XBeeException;
import com.rapplogic.xbee.api.zigbee.ZNetTxStatusResponse;

public class GeneralGUI extends JFrame implements ActionListener
{
        /* CONSTANTS */
        final String FILEPATH = "/home/pdesantis/Documents/testing/";   // Holds the default download directory for this program
        
        /* THREADS */
        QueueManager tQueueManager = new QueueManager();
        WiFiDataListener tWiFiDataListener = new WiFiDataListener();
        
        /* CLASS VARIABLES */
        public XBeeInterface xbee;
        public PacketListener packetListener;

        Socket clientSocket;
        ObjectOutputStream clientObjectOutputStream;
        ObjectInputStream clientObjectInputStream;

        public volatile boolean connectedWiFi;
        public volatile boolean transmissionInProgress;
        
        long scriptStartTime = 0;
        
        int port = 13267;

        volatile LinkedBlockingQueue<int[]> queueDataReceived = new LinkedBlockingQueue<int[]>();
        volatile LinkedBlockingQueue<FileInfo> queueFiles = new LinkedBlockingQueue<FileInfo>();
        public long queueBytes;
        public File file;
        volatile boolean fileTransferComplete;

        public FileInputStream fileInStream;
        public BufferedInputStream buffInStream;

        public FileOutputStream fileOutStream;
        public BufferedOutputStream buffOutStream;

        /* GUI VARIABLES */
        JPanel pane;
        JTextField devicePort = new JTextField("/dev/ttyUSB0", 15);
        JLabel devicePortLabel = new JLabel("Enter Device Port:");
        JButton startButton = new JButton("Enable SmartPower");
        JButton sendPacketButton = new JButton("Send Packet");
        JButton sendFileButton = new JButton("Send a File");
        JTextField fileLocation = new JTextField(FILEPATH + "file1", 40); 
        JLabel fileLocationLabel = new JLabel("Enter File Path:");

        boolean readyToSend;

        boolean selfReadyToReceive;
        boolean friendReadyToReceive;

        JTextArea outputBox = new JTextArea();;

        /* GUI METHODS */
        public GeneralGUI()
        {
                //FILEPATH = "/home/pdesantis/Documents/testing/";
                
                connectedWiFi = false;
                transmissionInProgress = false;
                readyToSend = true;
                selfReadyToReceive = false;
                friendReadyToReceive = false;

                setSize(600, 600);
                setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                /*
                MessageConsole mc = new MessageConsole(outputBox);
                mc.redirectOut(null, System.out);
                mc.setMessageLines(25);
                 */
                startButton.addActionListener(this);
                sendPacketButton.addActionListener(this);
                sendFileButton.addActionListener(this);
                pane = new JPanel();

                pane.add(devicePortLabel);
                pane.add(devicePort);

                pane.add(startButton);
                pane.add(sendPacketButton);
                pane.add(sendFileButton);
                pane.add(outputBox);
                
                pane.add(fileLocation);
                pane.add(fileLocationLabel);

        }

        // This is basically an "abstract class" here
        // It gets redefined in our subclasses
        public void actionPerformed(ActionEvent event)
        {
        }

        /* CLASS METHODS */
        public void openXBeeInterface(String XBeePort, int XBeeBaud)
        {
                XBeePort = "/dev/ttyUSB0";
                XBeeBaud = 9600;
                clientSocket = new Socket();
                try
                {
                        if(xbee == null)
                        {
                                xbee = new XBeeInterface(XBeePort, XBeeBaud);
                        }
                        else
                        {
                                //xbee.xbee.close();
                                //xbee.xbee.open(XBeePort, XBeeBaud);
                        }
                        tQueueManager.start();  // Start the Queue Manager Thread
                }
                catch (XBeeException e)
                {
                }
        }

        /* UTILITY FUNCTIONS */
        public int[] removeFirstArrayElement(int[] arr)
        {
                int[] new_arr;
                new_arr = new int[arr.length - 1];
                for(int i = 1; i < arr.length; i++)
                {
                        new_arr[i - 1] = arr[i];
                }
                return new_arr; 
        }

        public String removeFirstChar(String str)
        {
                String new_str = str.substring(1);
                return new_str;
        }
        /* END UTILITY FUNCTIONS */


        /* SEND/RECEIVE FUNCTIONS */
        public void sendFileTransferRequest(FileInfo fileInfo)
        {
                try {
                        Thread.sleep(100);
                } catch (InterruptedException e) {
                        e.printStackTrace();
                }
                String fileName;
                char[] fileSizeChar;

                // TODO add file selection box thingy

                fileName = fileInfo.getFileName();              // Read the name of the file
                long fileSize = fileInfo.getFileSize(); // Read the size of the file in bytes
                fileSizeChar = Long.toString(fileSize).toCharArray();   // Convert the file size (long) to a string

                // CREATE THE ZIGBEE PACKET
                // Create the payload integer array with the array size of 2 (for the status byte and separator), the length of the file name, and the length of the file size 
                // TODO add a check to see if the payload is still less than the max payload size
                int[] payload = new int[2 + fileName.length() + fileSizeChar.length];

                // Set the status byte to 3
                payload[0] = '3';

                // Break apart the fileName string into chars and add them to the payload 
                int i = 1;
                for(char oneChar : fileName.toCharArray())
                {
                        payload[i++] = oneChar;
                }

                payload[i++] = ',';             // Add a comma to separate the file name from the file size

                // Break apart the fileSizeChar array and add it to the payload
                for(char oneChar : fileSizeChar)
                {
                        payload[i++] = oneChar;
                }

                //System.out.println("Sending file transfer request for file: " + fileName);
                readyToSend = false;
                // Send the packet, check whether it was delivered successfully or not, and if not, repeat sending it until it succeeds
                while(xbee.XBeeSendPacket(payload) != ZNetTxStatusResponse.DeliveryStatus.SUCCESS);
                //xbee.XBeeSendPacket(payload);
                //xbee.XBeeSendPacket(payload); // Send the packet and wait for an ACK
                readyToSend = true;
                //System.out.println("File transfer request ACK received for file: " + fileName);

                //System.out.println("Adding file to queue to be sent: " + fileName);
                // Add this file to the queue of files to be sent
                queueFiles.add(fileInfo);
                queueBytes = queueBytes + fileInfo.getFileSize();
        }

        public void sendFile(FileInfo fileInfo)
        {
                transmissionInProgress = true;
                
                ArrayList<Integer> payloadTemp;
                int currentAmtBytesRead;
                int oneByte;
                long start;
                long end;
                int packetCounter = 0;

                boolean firstLoopZigBee = true;
                boolean firstLoopWiFi = true;

                try
                {
                        FileInputStream fileInStream = new FileInputStream(FILEPATH + fileInfo.getFileName());
                        BufferedInputStream buffInStream = new BufferedInputStream(fileInStream);

                        //System.out.println("Waiting for friend to be ready to receive");
                        int spacer = 0;
                        //System.out.println("");
                        while(!friendReadyToReceive)
                        {
                                /*
                                System.out.print(".");
                                spacer++;
                                if(spacer == 100)
                                {
                                        spacer = 0;
                                        System.out.println("");
                                }
                                 */
                                // sleep, so as not to kill CPU cycles
                                // is this even necessary?
                                try
                                {
                                        //System.out.print(".");
                                        Thread.sleep(10);
                                }
                                catch (InterruptedException e)
                                {
                                        e.printStackTrace();
                                }
                        }
                        //System.out.println("");
                        //System.out.println("Friend is ready to receive!");
                        /*
                        if(fileInfo.getbytesTouched() == fileInfo.getFileSize())
                        {
                                System.out.println("FUCK YOU PAT FUCK YOU PAT FUCK YOU PAT FUCK YOU PAT FUCK YOU PAT FUCK YOU PAT ");
                        }
                        */
                        
                        start = System.currentTimeMillis();
                        System.out.println("Sending started " + (start - scriptStartTime) + " milliseconds after the start of the script");
                        
                        while(fileInfo.getbytesTouched() != fileInfo.getFileSize())
                        {
                                //System.out.println("trying to send? readyToSend is: " + readyToSend);
                                currentAmtBytesRead = 0;        // reset the current amount of bytes read for this loop to 0
                                if(connectedWiFi)       // Wi-Fi
                                {
                                        if(firstLoopWiFi)
                                        {
                                                System.out.println("Sending the file through Wi-Fi: " + fileInfo.getFileName());
                                                firstLoopWiFi = false;
                                        }

                                        long bytesLeft = fileInfo.getFileSize() - fileInfo.getbytesTouched();

                                        FileBytes fileBytes = new FileBytes(bytesLeft);

                                        buffInStream.read(fileBytes.bytes);

                                        clientObjectOutputStream.writeObject(fileBytes);
                                        clientObjectOutputStream.flush();
                                        clientObjectOutputStream.reset();
                                        fileInfo.setbytesTouched(fileInfo.getbytesTouched() + fileBytes.bytes.length);
                                        currentAmtBytesRead = fileBytes.bytes.length;
                                }
                                else    // ZigBee
                                {
                                        if(readyToSend)
                                        {
                                                if(firstLoopZigBee)
                                                {
                                                        System.out.println("Sending the file through ZigBee: " + fileInfo.getFileName());
                                                        firstLoopZigBee = false;
                                                }

                                                payloadTemp = new ArrayList<Integer>(); // Reset the temporary payload list
                                                payloadTemp.add(new Integer(52));               // Set the first (status) byte to 52 (the decimal code for '4'), the number for FILE DATA   
                                                while(currentAmtBytesRead < 70)                 // Read (up to) 70 bytes from the file
                                                {
                                                        oneByte = buffInStream.read();
                                                        if(oneByte == -1)       // If there are no more bytes left in the file (there were less than 70 bytes left to read)
                                                        {
                                                                //System.out.println("FUCK ONLY SEE ME ONCE FUCK FUCK ONLY SEE ME ONCE FUCK FUCK ONLY SEE ME ONCE FUCK");
                                                                break;  // Done reading, break out of this loop!
                                                        }
                                                        else
                                                        {
                                                                payloadTemp.add(new Integer(oneByte));  // Add these bytes to the temporary payload array
                                                                currentAmtBytesRead++;                  // Increment the amount of bytes read
                                                        }
                                                }

                                                // Convert the arraylist to an integer array
                                                int[] payload = new int[payloadTemp.size()];
                                                for(int i = 0; i < payload.length; i++)
                                                {
                                                        payload[i]=((Integer)payloadTemp.get(i)).intValue();
                                                }


                                                //System.out.println("Sending packet with " + currentAmtBytesRead + " bytes of data");
                                                System.out.print(".");

                                                packetCounter++;
                                                if(packetCounter == 49)
                                                {
                                                        packetCounter = 0;
                                                        System.out.println("");
                                                }

                                                fileInfo.setbytesTouched(fileInfo.getbytesTouched() + currentAmtBytesRead);
                                                // Send the packet, check whether it was delivered successfully or not, and if not, repeat sending it until it succeeds
                                                //while(xbee.XBeeSendPacket(payload) != ZNetTxStatusResponse.DeliveryStatus.SUCCESS);
                                                xbee.XBeeSendPacket(payload);
                                        }
                                        else
                                        {
                                                try
                                                {
                                                        Thread.sleep(10);
                                                }
                                                catch (InterruptedException e)
                                                {
                                                        e.printStackTrace();
                                                }
                                        }
                                }
                                queueBytes = queueBytes - currentAmtBytesRead;          // Decrease queueBytes by the amount of bytes just sent
                        }
                        friendReadyToReceive = false;
                        //System.out.println("Sent file: " + fileInfo.fileName + " , bytes: " + fileBytes.bytes.length);
                        end = System.currentTimeMillis();
                        //System.out.println("");
                        long bandwidth = 0;
                        if((end-start) != 0)
                                 bandwidth = ((fileInfo.getFileSize() / (end-start)) * 1000);
                        System.out.println("Sent file. File: "  + fileInfo.getFileName() + " , Size: " + fileInfo.getFileSize() + " bytes, time: " + (end-start) + " milliseconds, bandwidth: " + bandwidth + " bytes/second");
                        System.out.println("Sending completed " + (end - scriptStartTime) + " milliseconds after the start of the script" + " (" + new Date().toString() + ")");
                        System.out.println("queueBytes = " + queueBytes);
                        System.out.println("");
                        buffInStream.close();   // Close the buffered input stream
                        fileInStream.close();   // Close the file input stream
                }
                catch (FileNotFoundException e)
                {
                        e.printStackTrace();
                }
                catch (IOException e)
                {
                        e.printStackTrace();
                }
        }

        public void receiveFile(FileInfo file)
        {
                int[] bytesToWrite;
                int bytesWritten = 0;
                long start;
                long end;
                int packetCounter = 0;

                try
                {
                        fileOutStream = new FileOutputStream(FILEPATH + file.getFileName());    // set up the Output Streams
                        buffOutStream = new BufferedOutputStream(fileOutStream);

                        System.out.println("Receiving file. File: "  + file.getFileName());
                        //System.out.println("Output Streams set up");

                        start = System.currentTimeMillis();
                        System.out.println("Receiving started " + (start - scriptStartTime) + " milliseconds after the start of the script");
                        //System.out.println("Sending ready to receive packet!");
                        selfReadyToReceive = true;
                        
                        int[] payload = new int[] { '5' };
                        while(xbee.XBeeSendPacket(payload) != ZNetTxStatusResponse.DeliveryStatus.SUCCESS);     // Send the packet, check whether it was delivered successfully or not, and if not, repeat sending it until it succeeds

                        while(file.getFileSize() - file.getbytesTouched() > 0)  // While the file has not been completely received
                        {
                                bytesToWrite = queueDataReceived.take();        // Wait until data has been received, then take it off the queue...
                                //System.out.println("Took data off the data received queue: " + bytesToWrite.length);
                                for(int oneByte : bytesToWrite)
                                {
                                        buffOutStream.write(oneByte);                   // ...and write it to the output stream
                                        //System.out.println("Writing one byte: " + oneByte);
                                }
                                //System.out.println("Wrote " + bytesToWrite.length + " bytes");
                                //System.out.print(".");
                                /*
                                packetCounter++;
                                if(packetCounter == 50)
                                {
                                        packetCounter = 0;
                                        //System.out.println("");
                                }
                                */
                                file.setbytesTouched(file.getbytesTouched() + bytesToWrite.length);     // Update the amount of bytes written
                                bytesWritten = bytesWritten + bytesToWrite.length;
                                queueBytes = queueBytes - bytesToWrite.length;  // Subtract the number of bytes written from the queue size
                        }
                        end = System.currentTimeMillis();
                        //System.out.println("");
                        long bandwidth = 0;
                        if((end-start) != 0)
                                 bandwidth = ((file.getFileSize() / (end-start)) * 1000);
                        System.out.println("File received. File: "  + file.getFileName() + " , Size: " + file.getFileSize() + " bytes, time: " + (end-start) + " milliseconds, bandwidth: " + bandwidth + " bytes/second");
                        System.out.println("Sending completed " + (end - scriptStartTime) + " milliseconds after the start of the script" + " (" + new Date().toString() + ")");
                        System.out.println("queueBytes = " + queueBytes);
                        System.out.println("");
                        selfReadyToReceive = false;
                        buffOutStream.flush();
                        buffOutStream.close();
                        fileOutStream.close();
                }
                catch (FileNotFoundException e)
                {
                        e.printStackTrace();
                }
                catch (IOException e)
                {
                        e.printStackTrace();
                }
                catch (InterruptedException e)
                {
                        e.printStackTrace();
                }
        }
        /* END SEND/RECEIVE FUNCTIONS */


        /* THREAD CLASSES */
        // Takes files off the queue and sends them to the appropriate handler function
        class QueueManager implements Runnable
        {
                private volatile Thread threadQueueManager;
                FileInfo currentFile;

                void stop()
                {
                        threadQueueManager = null;
                }

                void start()
                {
                        threadQueueManager = new Thread(this);
                        threadQueueManager.start();
                }

                public void run()
                {
                        Thread thisThread = Thread.currentThread();
                        //System.out.println("Started the queue manager thread");
                        while(threadQueueManager == thisThread)
                        {
                                try
                                {
                                        //System.out.println("Waiting to take a file off the queue");
                                        currentFile = queueFiles.take();
                                        if(currentFile.isToSend())
                                        {
                                                //System.out.println("File taken off the queue to be sent: " + currentFile.getFileName());
                                                sendFile(currentFile);                  // wait until file is sent
                                                //System.out.println("File sent!");
                                        }
                                        else
                                        {
                                                //System.out.println("File taken off the queue to be received: " + currentFile.getFileName());
                                                receiveFile(currentFile);               // wait until file is received
                                                //System.out.println("File received!");
                                        }
                                }
                                catch (InterruptedException e)
                                {
                                        e.printStackTrace();
                                }
                        }
                }
        }

        // Takes packet from received from socket and adds it to the data queue
        class WiFiDataListener implements Runnable
        {
                private volatile Thread threadWiFiDataListener;

                void stop()
                {
                        threadWiFiDataListener = null;
                }

                void start()
                {
                        threadWiFiDataListener = new Thread(this);
                        threadWiFiDataListener.start();
                }

                public void run()
                {
                        Thread thisThread = Thread.currentThread();
                        try
                        {
                                while(threadWiFiDataListener == thisThread)
                                {
                                        if(connectedWiFi)
                                        {
                                                try
                                                {
                                                        //if(clientObjectInputStream.available() > 0)
                                                        //{
                                                                //DataInputStream testy;
                                                                //testy.
                                                                FileBytes fileBytes = (FileBytes) clientObjectInputStream.readObject();
                                                                //System.out.println("WiFi Listener received: " + inByte);
                                                                queueDataReceived.add(fileBytes.getBytesAsIntArray());
                                                        //}
                                                                /*
                                                        else
                                                        {
                                                                try
                                                                {
                                                                        Thread.sleep(10);
                                                                }
                                                                catch (InterruptedException e)
                                                                {
                                                                }
                                                        }
                                                        */
                                                }
                                                catch (ClassNotFoundException e)
                                                {
                                                        e.printStackTrace();
                                                }
                                                catch(SocketException e)
                                                {
                                                        //e.printStackTrace();
                                                        //System.out.println("SILLY WILLY SILLY WILLY SILLY WILLY SILLY WILLY SILLY WILLY ");
                                                }
                                        }
                                }
                        }
                        catch (IOException e)
                        {
                                e.printStackTrace();
                        }
                }
        }
}